#include <assert.h>
#include <time.h>
#include <string.h>
#include "socket_wrapper.h"
#include "socket_inc.h"
#include <core/QbLogger.h>

#if defined(__ANDROID__)
#define SO_NOSIGPIPE MSG_NOSIGNAL
#endif

namespace qb
{
#ifdef _MSC_VER
	class SockedInit
	{
	public:
		SockedInit()
		{
			WSAData wsaData;
			if (::WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
			{
				log_error("socket init failed");
			}
		}
		~SockedInit()
		{
			if (::WSACleanup() != 0)
			{
				log_warning("socket uninit failed(GetSockError=%d)",CSocketWrapper::GetSockError());
			}
		}
	};
	static SockedInit g_sock_init_inst;
#endif
	int32_t CSocketWrapper::GetSockError()
	{
#ifdef _MSC_VER
		return WSAGetLastError();
#else
		return errno;
#endif
	}
	CSocketWrapper::CSocketWrapper()
		: m_closed(true)
		, m_uTargetIP(0)
		, m_uTargetPort(0)
		, m_socket(INVALID_SOCKET)
	{
		m_addr_info = new sockaddr_in;
	}

	CSocketWrapper::~CSocketWrapper()
	{
		delete static_cast<sockaddr_in*>(m_addr_info);
	}

	int32_t CSocketWrapper::Create(uint16_t socket_type)
	{
		if (m_socket != INVALID_SOCKET)
		{
			Close();
		}
		if (socket_type == IPPROTO_TCP)
		{
			m_socket = (uint32_t)::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
		}
		else
		{
			m_socket = (uint32_t)::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 
		}
		if (m_socket == SOCKET_ERROR)   
		{
			m_socket = INVALID_SOCKET;
			log_error("socket failed(% d)",GetSockError());
			return -1;   
		}

		m_closed = false;
		return 0;
	}

	int32_t CSocketWrapper::Close()
	{
		if (!m_closed)
		{
			m_closed = true;
		}
		if (m_socket != INVALID_SOCKET)
		{
#ifdef _MSC_VER
			closesocket (m_socket);
#else
			close(m_socket);
#endif
			m_socket = INVALID_SOCKET;
		}

		return 0;
	}

	int32_t CSocketWrapper::SetOpt()
	{
		int32_t ret = 0;
		static int32_t nSendSize = 128 * 1024;
		static int32_t nRecvSize = 128 * 1024;
		if(SOCKET_ERROR == ::setsockopt(m_socket, SOL_SOCKET,
			SO_SNDBUF, (char*)&nSendSize, sizeof(nSendSize)))
		{
			log_warning("setsockopt(SO_SNDBUF) failed(%d)",GetSockError());
			ret = -1;
		}
		if(SOCKET_ERROR == ::setsockopt(m_socket, SOL_SOCKET,
			SO_RCVBUF, (char*)&nRecvSize, sizeof(nRecvSize)))
		{
			log_warning("setsockopt(SO_RCVBUF) failed(%d)",GetSockError());
			ret = -1;
		}

		return ret;
	}

	int32_t CSocketWrapper::SetTcpOpt()
	{
		int32_t ret = 0;
#ifdef _MSC_VER
		static int32_t nFlag = 1;
		
		if(SOCKET_ERROR == ::setsockopt(m_socket, IPPROTO_TCP,TCP_NODELAY , (char*)&nFlag, sizeof(nFlag)))
		{
			log_warning("setsockopt(TCP_NODELAY) failed(%d)",GetSockError());
			ret = -1;
		}
#else
        int set = 1;
        //::setsockopt(m_socket, SOL_SOCKET, SO_NOSIGPIPE, (void*)&set, sizeof(int));

#ifdef __linux__

	setsockopt(m_socket, SOL_SOCKET, MSG_NOSIGNAL, &set, sizeof(int));

#else

	setsockopt(m_socket, SOL_SOCKET, SO_NOSIGPIPE, &set, sizeof(int));

#endif


#endif

		return ret;
	}

	int32_t CSocketWrapper::SetKeepalive()
	{
#ifdef _MSC_VER
		const char chOpt = 1; // True
		// Set KeepAlive 开启保活机制, 防止服务端产生死连接
		if (setsockopt(m_socket, SOL_SOCKET, SO_KEEPALIVE, (char *)&chOpt, sizeof(chOpt)) == 0)
		{
			DWORD cbBytesReturned = chOpt;
			tcp_keepalive klive;
			klive.onoff = 1; // 启用保活
			klive.keepalivetime = 1000 * 60 * 1; // 1分钟超时 Keepalive
			klive.keepaliveinterval = 1000 * 5; // 重试间隔为5秒 Resend if No-Reply
			WSAIoctl(m_socket, SIO_KEEPALIVE_VALS,&klive,sizeof(tcp_keepalive),NULL,0,&cbBytesReturned,0,NULL);
			return 0;
		}
#endif
        
		return 0;
	}

	int32_t CSocketWrapper::BindRandPort(uint16_t& local_port)
	{
		int32_t ret = -1;
		srand((uint32_t)time(NULL));

		sockaddr_in localAddr;
		memset( &localAddr, 0, sizeof( localAddr ));
		localAddr.sin_family = AF_INET;
		localAddr.sin_addr.s_addr = htonl(INADDR_ANY);

		for (int32_t i = 0; i < 10; i++)
		{
			uint16_t port = (60000 - 2048 + 1) * rand() + 2048;
			localAddr.sin_port = htons( port );
			if (bind(m_socket, (sockaddr*)&localAddr, sizeof(localAddr)) == 0)
			{
				local_port = port;
				ret = 0;
				break;
			}
		}

		return ret;
	}
	
	int32_t CSocketWrapper::GetFirstIPAddress(std::string& local_ipaddress)
	{
		char szHostName[128] = {0};
		if (gethostname(szHostName, 128) == 0)
		{
			hostent* ent = gethostbyname(szHostName);
			if (ent)
			{
				local_ipaddress = inet_ntoa(*(in_addr *)ent->h_addr_list[0]);
				return 0;
			}
		}
		log_error("dns gethostbyname(%s) falied, getlasterror(%d)",
			local_ipaddress.c_str(),GetSockError());
		return -1;
	}

	int32_t CSocketWrapper::GetIPAddrInfo(const char* ipaddress,uint16_t port)
	{
		addrinfo hints;
		addrinfo* results	= NULL;
		sockaddr_in* addr_info_tmp = (sockaddr_in*)(m_addr_info);

		memset(&hints, 0, sizeof(addrinfo));
		hints.ai_family		= PF_UNSPEC;
		hints.ai_socktype	= SOCK_DGRAM;
		hints.ai_protocol	= IPPROTO_UDP;

		int gaiResult = getaddrinfo(ipaddress, NULL, &hints, &results);
		if (gaiResult != 0 || !results || results->ai_family != AF_INET)
		{
			return -1;
		}

		memcpy((void*)addr_info_tmp,(void*)((sockaddr_in*)results->ai_addr),sizeof(sockaddr_in));
		addr_info_tmp->sin_port = htons(port);
		freeaddrinfo(results);
		
		return 0;
	}

	int32_t CSocketWrapper::Connect(const char* ipaddress,uint16_t port)
	{
		hostent* pHostent = gethostbyname(ipaddress);
		if (!pHostent)
		{
			log_error("gethostbyname failed(%s,err=%d)",ipaddress,GetSockError());
			return -1;
		}

		int32_t flags = SetNBIO();

		// get connect address data
		sockaddr_in	clientAddr;
		clientAddr.sin_family	= AF_INET;
		clientAddr.sin_port		= htons(port);
		clientAddr.sin_addr		= *((struct in_addr *)pHostent->h_addr);

		// record target information
		m_uTargetIP = clientAddr.sin_addr.s_addr;
		m_uTargetPort = port;

		// connect
		int32_t ret = ::connect(m_socket, (sockaddr*)&clientAddr,sizeof(clientAddr));
		if (ret == 0)
		{
			SetBIO(flags);
			return 0;
		}
#ifdef _MSC_VER
		else if (ret < 0 && GetSockError() == WSAEWOULDBLOCK )
#else
		else if (ret < 0 && errno == EINPROGRESS )
#endif
		{
			int32_t time_count = 20;
			while (--time_count >= 0 && m_socket != INVALID_SOCKET)
			{
				fd_set fd;
				FD_ZERO(&fd);
				FD_SET(m_socket, &fd);
				struct timeval tv;
				tv.tv_sec = 0;
				tv.tv_usec = 500000; // 500ms

				int32_t ret = select(m_socket + 1, NULL, &fd, NULL, &tv);  
				if (ret == SOCKET_ERROR)
				{
					if (IsClosed())
					{
						return 0;
					}
					log_error("select falied, getlasterror(%d)",GetSockError());

					return -1;
				}
				else if (ret == 0)
				{
					continue;
				}
				else
				{
					if (m_socket == INVALID_SOCKET)
					{
						return 0;
					}
					if (FD_ISSET(m_socket, &fd))
					{
						int32_t error = 0;
						socklen_t len = sizeof(error);
						int32_t ret = getsockopt(m_socket, SOL_SOCKET, SO_ERROR, (char*)&error, &len);
						// 如果发生错误，Solaris实现的getsockopt返回-1,把pending error设置给errno.
						// Berkeley实现的getsockopt返回0, pending error返回给error.
						// 我们需要处理这两种情况
						if (ret < 0 || error)
						{
							log_error("FD_ISSET falied, ret(%d),getlasterror(%d)",ret,GetSockError());

							return -1; 
						}
						SetBIO(flags);
						return 0;
					}
					else
					{
						log_error("FD_ISSET falied,getlasterror(%d)",ret,GetSockError());

						return -1; 
					}
				}
			}
			if (IsClosed())
			{
				return 0;
			}
			if (time_count < 0)
			{
				log_error("select falied(timeout count 10), getlasterror(%d)",GetSockError());

				return -1;
			}
		}
		else
		{
			log_error("connect falied, getlasterror(%d)",GetSockError());
			return -1;
		}

		return -1;
	}

	int32_t CSocketWrapper::SetNBIO()
	{
#ifdef _MSC_VER
		u_long tmp = 1;
		if (ioctlsocket(m_socket, FIONBIO, &tmp) < 0)  
		{  
			log_error("ioctlsocket falied, mode(1),getlasterror(%d)",GetSockError());
		}
		return 0;
#else
		int32_t flags = fcntl(m_socket, F_GETFL, 0);
		flags = flags | O_NONBLOCK;
		fcntl(m_socket, F_SETFL, flags | O_NONBLOCK);

		return flags;
#endif
	}

	int32_t CSocketWrapper::SetBIO(int32_t mode)
	{
#ifdef _MSC_VER
		u_long tmp = mode;
		if (ioctlsocket(m_socket, FIONBIO, &tmp) < 0)  
		{  
			log_error("ioctlsocket falied, getlasterror(%d)",GetSockError());
		}
#else
		fcntl(m_socket, F_SETFL, mode);
#endif
		return 0;
	}

#if _WIN32
	uint32_t CSocketWrapper::GetSocket()
#else
    int32_t CSocketWrapper::GetSocket()
#endif
	{
		return m_socket;
	}

	int32_t CSocketWrapper::SendTo(const char* buff,uint32_t len)
	{
		if (IsClosed())
		{
			return -1;
		}
		int32_t numBytes = sendto(m_socket, buff, len,0,
			(const sockaddr*)(m_addr_info), sizeof(sockaddr_in));

		return numBytes;
	}

	int32_t CSocketWrapper::Send(const char* buff,uint32_t len)
	{
		if (IsClosed())
		{
			return -1;
		}
		int32_t ret = send(m_socket,buff,len,0);

		return ret;
	}

	int32_t CSocketWrapper::Select()
	{
		if (IsClosed())
		{
			return -1;
		}

		while (!IsClosed())
		{
			fd_set fdRead;
			FD_ZERO(&fdRead);
			FD_SET(m_socket, &fdRead);

			struct timeval tv;
			tv.tv_sec = 0;
			tv.tv_usec = 200000; // 200ms

			int32_t ret = select(m_socket + 1, &fdRead, NULL, NULL, &tv);
			if (ret > 0)
			{
				return ret;
			}
			else if (ret < 0)
			{
				break;
			}
		}

		return -1;
	}

	int32_t CSocketWrapper::Recv(char* buff,uint32_t len)
	{
		if (IsClosed())
		{
			return -1;
		}

		int32_t ret = recv(m_socket, buff, len, 0);

#ifdef _MSC_VER
		// The connection has been gracefully closed
		if (0 == ret)
		{
			log_error( "recv error: [connection closed]!");
			return -2;
		}

		// Success, and return recv size
		if (SOCKET_ERROR != ret)
		{
			return ret;
		}

		// Operation now in progress
		uint32_t err_code = GetSockError();
		if (WSAEINPROGRESS == err_code)
		{
			log_warning("recv error: [WSAEINPROGRESS]");
			return 0;
		}
		
		// Message too long
		if (WSAEMSGSIZE == err_code)
		{
			log_warning("recv error: [WSAENOBUFS]");
			return 0;
		}

		// No buffer space available
		if (WSAENOBUFS == err_code)
		{
			log_warning("%recv error: [WSAENOBUFS]");
			return -3;
		}

		log_error( "recv error: %d", err_code);
		return -1;
#else
		return ret;
#endif
	}

	int32_t CSocketWrapper::Recvfrom(char* buff,uint32_t len,void* props)
	{
		if (IsClosed())
		{
			return -1;
		}
		socklen_t propLen = sizeof(sockaddr_in);
		int32_t numBytes = recvfrom(m_socket, buff, len, 0, (sockaddr*)props, &propLen);

		return numBytes;
	}

	uint32_t CSocketWrapper::GetTargetIP()
	{
		return m_uTargetIP;
	}

	uint16_t CSocketWrapper::GetTargetPort()
	{
		return m_uTargetPort;
	}

	bool CSocketWrapper::CompareAddrInfo(void* addr_info)
	{
		sockaddr_in* dst_addr_info = (sockaddr_in*)addr_info;
		sockaddr_in* src_addr_info = (sockaddr_in*)m_addr_info;
		if (memcmp(&dst_addr_info->sin_addr, &src_addr_info->sin_addr, sizeof(in_addr)) != 0
			|| dst_addr_info->sin_port != src_addr_info->sin_port)
		{
			return false;
		}

		return true;
	}

	bool CSocketWrapper::IsInvalid()
	{
		return GetSocket() == INVALID_SOCKET;
	}

	bool CSocketWrapper::IsClosed()
	{
		return m_closed;
	}
}
